package org.hackystat.devcathlon.engine.team;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.hackystat.devcathlon.engine.db.util.HibernateUtil;
import org.hackystat.devcathlon.engine.user.User;
import org.hackystat.devcathlon.engine.user.UserManager;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction;

/**
 * A thread-safe singleton class that manages access to the Teams in this system. 
 * @author Philip Johnson
 */
public class TeamManager {
  /** The singleton instance of this class. */
  private static TeamManager theInstance = new TeamManager();
  /** The (team name, team instance) tuples. */
  private Map<String, Team> names2teams = new HashMap<String, Team>();
  /** The private constructor. */
  private TeamManager() {
    // does nothing at this point; will initialize from database later.
  }

  /**
   * Returns the singleton instance of the TeamManager.
   * @return The TeamManager.
   */
  public static TeamManager getInstance() {
    return TeamManager.theInstance;
  }

  /**
   * Adds a new team to the system. 
   * @param name The name of the new team. Must be unique.
   * @param owner The user who owns this team.  
   * @param users The set of Users on this team. 
   * @return The new team instance. 
   * @throws TeamException If the team name is not unique. 
   */
  public synchronized Team addTeam(String name, User owner, Set<User> users) throws TeamException {
//    if (this.names2teams.containsKey(name)) {
//      throw new TeamException("Attempt to add team with duplicate name: " + name);
//    }
    Team team = getTeam(name);
    Session session = HibernateUtil.getSessionFactory().getCurrentSession();
    try {
      session.beginTransaction();
      if (team == null) {
        team = new Team(name, owner, users);
        this.names2teams.put(name, team);
        users.add(owner);
        team.setMembers(users);
        session.save(team);
        System.out.println("Created Team: " + team.getName());
      }
      session.getTransaction().commit();
    }
    catch (HibernateException ex) {
      Transaction tx = session.getTransaction();
      if (tx.isActive()) {
        tx.rollback();
      }
      throw ex;
    }
    return team;
  }
  
  /**
   * Returns the Team instance associated with the name, or null if not found. 
   * @param name The name of the team to be retrieved. 
   * @return The Team instance, or null if not found. 
   */
  public synchronized Team getTeam (String name) {
//    return this.names2teams.get(name);
    Session session = HibernateUtil.getSessionFactory().getCurrentSession();
    String hqlSelect = "from Team t where t.name = :name";
    Team team = null;
    try {
      session.beginTransaction();
      Query query = session.createQuery(hqlSelect).setString("name", name);
      query.setMaxResults(1);
      team = (Team) query.uniqueResult();
//      team = (Team) session.createCriteria(Team.class)
//              .setFetchMode("owner", FetchMode.JOIN)
//              .setFetchMode("members", FetchMode.JOIN)
//              .add(Restrictions.eq("name", name))
//              .uniqueResult();
      session.getTransaction().commit();
    }
    catch (HibernateException ex) {
      Transaction tx = session.getTransaction();
      if (tx.isActive()) {
        tx.rollback();
      }
//      throw ex;
    }
    return team;
  }  
  
  /**
   * Returns the List of teams associated with the user.
   * @param email The member to look for.
   * @return The list of teams the member is associated to.
   */
  public synchronized List<Team> getTeams (String email) {
    List<Team> memberTeams = new ArrayList<Team>();
    for (Team team : this.getAllTeams()) {
      if (team.getOwner().getEmail().equals(email)) {
        memberTeams.add(team);        
      }
      else {
        for (User user : team.getMembers()) {          
          if (user.getEmail().equals(email)) {                        
            memberTeams.add(team);
          }
        }
      }
    }
    return memberTeams;
  }
  
  /**
   * Set the list of members being invited for a given team.
   * @param teamName The team name.
   * @param teamMembers The set of team members.
   */
  public synchronized void setTeamInvites(String teamName, Set<User> teamMembers) {
    Team team = this.getTeam(teamName);
    Session session = HibernateUtil.getSessionFactory().getCurrentSession();
    try {
      if (null != team) {
        session.beginTransaction();
        team.setInvited(teamMembers);
        session.saveOrUpdate(team);
        session.getTransaction().commit();
      }
    }
    catch (HibernateException ex) {
      Transaction tx = session.getTransaction();
      if (tx.isActive()) {
        tx.rollback();
      }
      throw ex;
    }
  }
  
  /**
   * Returns the list of teams the user was invited to.
   * @param email The email of the user.
   * @return The list of teams.
   */
  public synchronized List<Team> getTeamInvites(String email) {
    List<Team> invitedTo = new ArrayList<Team>();
    User user = UserManager.getInstance().getUser(email);
    for (Team team : this.getAllTeams()) {
      if (team.getInvited().contains(user)) {
        invitedTo.add(team);
      }
    }
    return invitedTo;
  }
  
  /**
   * Returns the list of teams that are the user's is not apart of.
   * @param email The user's email.
   * @return The list of opponents.
   */
  public synchronized List<Team> getPossibleOpponent (String email) { 
    List<Team> opponentTeam = new ArrayList<Team>(); 
    boolean isNotMember = true;
    for (Team team : this.getAllTeams()) {
      isNotMember = true;
      if (team.getOwner().getEmail().equals(email)) { 
        isNotMember = false;
      } 
      else { 
        for (User user : team.getMembers()) { 
          if (user.getEmail().equals(email)) { 
            isNotMember = false;
          } 
        } 
      }
      if (isNotMember) { 
        opponentTeam.add(team);
      }
    }
    return opponentTeam; 
  }
  
  /**
   * Retrieves teams where the user is the owner of.
   * Populating the manage page with this until "resign" function is implemented.
   * @param email The user to look for.
   * @return The list of teams that the user owns.
   */  
  public synchronized List<Team> getOwnedTeams(String email) {
    List<Team> userOwnedTeams = new ArrayList<Team>();
    for ( Team team : getTeams(email)) {
      if (team.getOwner().getEmail().equals(email)) {
        userOwnedTeams.add(team);
      }
    }
    return userOwnedTeams;    
  }
  
  /**
   * Retrieves teams where the user is only a member of.   
   * @param email The user to look for.
   * @return The list of teams that the user owns.
   */  
  public synchronized List<Team> getUnownedTeams(String email) {
    List<Team> userUnownedTeams = new ArrayList<Team>();
    for ( Team team : getTeams(email)) {
      if ( !(team.getOwner().getEmail().equals(email)) ) {
        userUnownedTeams.add(team);
      }
    }
    return userUnownedTeams;    
  }  
  
  /**
   * Removes the specified team from the map of teams.
   * Does nothing if the team is not in the map.
   * @param name The name of the team to remove. 
   */
  public synchronized void deleteTeam(String name) {
    this.names2teams.remove(name);
    Team team = getTeam(name);
    Session session = HibernateUtil.getSessionFactory().getCurrentSession();
    try {
      session.beginTransaction();
      if (team != null) {
        session.delete(team);
      }
      session.getTransaction().commit();
    }
    catch (HibernateException ex) {
      Transaction tx = session.getTransaction();
      if (tx.isActive()) {
        tx.rollback();
      }
//      throw ex;
    }
  }
  
  /**
   * Updates the team name in the TeamManager.
   * May need to add additional lines if old team is 
   * still referenced by another manager.
   * 
   * @param oldName The old team name.
   * @param newName The new team name.
   */
  public synchronized void updateTeamName(String newName, String oldName) {
    Team team = this.names2teams.remove(oldName);
    team.setTeamName(newName);
    this.names2teams.put(newName, team);
  }  
  
  /**
  * Retrieves all teams in the TeamManager.
  * @return The list containing all teams.
  */
  @SuppressWarnings("unchecked") 
  public synchronized List<Team> getAllTeams() {
//    return new ArrayList<Team>(this.names2teams.values());
    Session session = HibernateUtil.getSessionFactory().getCurrentSession();
    String hqlSelect = "from Team";
    List<Team> teams = null;
    try {
      session.beginTransaction();
      Query query = session.createQuery(hqlSelect);
      teams = query.list();
      session.getTransaction().commit();
    }
    catch (HibernateException ex) {
      Transaction tx = session.getTransaction();
      if (tx.isActive()) {
        tx.rollback();
      }
//      throw ex;
    }
    return teams;
  }
  
  /**
   * Updates the team project name.
   * @param teamName The team name.
   * @param name The project name.
   */
  public synchronized void setProjectName(String teamName, String name) {
    Team team = this.getTeam(teamName);
    Session session = HibernateUtil.getSessionFactory().getCurrentSession();
    try {
      if (null != team) {
        session.beginTransaction();
        team.setProjectName(name);
        session.update(team);
        session.getTransaction().commit(); 
      }
    }
    catch (HibernateException ex) {
      Transaction tx = session.getTransaction();
      if (tx.isActive()) {
        tx.rollback();
      }
      throw ex;
    }
  }
  
  /**
   * Updates the team description.
   * @param teamName The team name.
   * @param info The team description.
   */
  public synchronized void setDescription(String teamName, String info) {
    Team team = this.getTeam(teamName);
    Session session = HibernateUtil.getSessionFactory().getCurrentSession();
    try {
      if (null != team) {
        session.beginTransaction();
        team.setDescription(info);
        session.update(team);
        session.getTransaction().commit(); 
      }
    }
    catch (HibernateException ex) {
      Transaction tx = session.getTransaction();
      if (tx.isActive()) {
        tx.rollback();
      }
      throw ex;
    }
  }
  
  /**
   * Updates the team logo path.
   * @param teamName The team name.
   * @param path The team logo path.
   */
  public synchronized void setTeamLogoPath(String teamName, String path) {
    Team team = this.getTeam(teamName);
    Session session = HibernateUtil.getSessionFactory().getCurrentSession();
    try {
      if (null != team) {
        session.beginTransaction();
        team.setTeamLogoPath(path);
        session.update(team);
        session.getTransaction().commit(); 
      }
    }
    catch (HibernateException ex) {
      Transaction tx = session.getTransaction();
      if (tx.isActive()) {
        tx.rollback();
      }
      throw ex;
    }
  }
  
  /**
   * Updates a given team by merging another instance to the original.
   * @param teamName The team name.
   * @param teamObj The updated team instance.
   * @param Team The updated team.
   */
  public synchronized void updateTeam(Team team) {
    Session session = HibernateUtil.getSessionFactory().getCurrentSession();
    try {
      session.beginTransaction();
      session.update(team);
      session.getTransaction().commit();
    }
    catch (HibernateException ex) {
      Transaction tx = session.getTransaction();
      if (tx.isActive()) {
        tx.rollback();
      }
      throw ex;
    }
  }
}
